package jf.collections;

import jf.closure.F1;
import jf.closure.F1B;
import static jf.collections.CollectionF.newList;
import static jf.collections.Tuple.asT;

import static java.lang.Math.max;
import static java.lang.Math.min;
import java.util.*;

/**
 * @author alex
 * @date 17.05.2009
 */
public abstract class AbstractListX<A> extends AbstractCollectionX<A> implements ListX<A> {

	protected abstract List<A> getBaseCollection();

	public abstract <B> ListX<B> createNewList(int capacity);

	public abstract <B> ListX<B> createNewList();

	public abstract <B> ListX<B> createNewList(Collection<? extends B> inital);

	public ListX<A> filter(F1B<A> filter) {
		ListX<A> list = createNewList();
		for (A a : this) {
			if (filter.apply(a)) {
				list.add(a);
			}
		}
		return list;
	}

	public <B> ListX<B> map(F1<A, B> transformer) {
		ListX<B> list = createNewList(this.size());
		for (A a : this) {
			list.add(transformer.apply(a));
		}
		return list;
	}

	public ListX<A> slice(int from, int to) {
		return subList(from, to);
	}

	public ListX<A> drop(int n) {
		int size = this.size();
		if (n > size) {
			return createNewList(0);
		} else if (n < 0) {
			throw new IllegalArgumentException("n can't be less than zero");
		} else {
			return this.subList(n, size);
		}
	}

	public ListX<A> dropRight(int n) {
		int size = this.size();
		if (n > size) {
			throw new IllegalArgumentException("n can't be greater than size");
		} else if (n < 0) {
			throw new IllegalArgumentException("n can't be less than zero");
		} else {
			return this.subList(0, size - n);
		}
	}

	@Override
	public ListX<A> dropWhile(F1B<A> f) {
		int i = 0;
		for (A a : this) {
			if (!f.apply(a)) {
				return this.subList(i);
			}
			i++;
		}
		return createNewList();
	}

	public <B> ListX<B> flatMap(F1<A, Iterable<B>> f) {
		ListX<B> list = createNewList();
		for (A a : this) {
			Iterable<B> iterable = f.apply(a);
			for (B b : iterable) {
				list.add(b);
			}
		}
		return list;
	}

	@SuppressWarnings({"unchecked"})
	private List flattenCollection(Collection list) {
		List result = newList();
		for (Object o : list) {
			if (o != null && o instanceof Collection) {
				List c = flattenCollection((Collection) o);
				for (Object o1 : c) {
					result.add(o1);
				}
			} else {
				result.add(o);
			}
		}
		return result;
	}

	@SuppressWarnings("unchecked")
	public <B> ListX<B> flatten() {
		if (this.isEmpty()) return createNewList();
		List result = flattenCollection(this);
		ListX<B> bList = createNewList();
		for (Object o : result) {
			bList.add((B) o);
		}
		return bList;
	}

	public ListX<A> remove(F1B<A> f) {
		ListX<A> list = createNewList(this.size());
		for (A a : this) {
			if (!f.apply(a)) {
				list.add(a);
			}
		}
		return list;
	}

	public ListX<A> removeDuplicates() {
		return createNewList(new LinkedHashSet<A>(this));
	}

	@SuppressWarnings("unchecked")
	public <S extends A> ListX<S> unsafeWiden() {
		return (ListX<S>) this;
	}

	@SuppressWarnings({"unchecked"})
	public <S> ListX<S> unsafeCastToOtherList() {
		return (ListX<S>) this;
	}

	@Override
	@SuppressWarnings({"unchecked"})
	public <C extends ListX<? super A>> C safe() {
		return (C)this;
	}

	@SuppressWarnings("unchecked")
	public ListX<A> sort() {
		ListX<A> list = createNewList(this);
		if (list.isEmpty()) {
			return list;
		} else {
			if ((list.get(0) instanceof Comparable)) {
				List<Comparable> cmp = (List<Comparable>) list;
				Collections.sort(cmp);
				return (ListX<A>) cmp;
			} else {
				return list;
			}
		}
	}

	public ListX<A> sort(Comparator<A> c) {
		ListX<A> list = createNewList(this);
		Collections.sort(list, c);
		return list;
	}

	public ListX<A> tail() {
		return this.subList(1);
	}

	public ListX<A> begin() {
		return this.subList(0, size() - 1);
	}

	public <B> ListX<Tuple2<A, B>> zip(List<B> that) {
		if (that.size() != this.size()) {
			throw new IllegalArgumentException("that size have to equals with this size");
		}
		ListX<Tuple2<A, B>> list = createNewList(this.size());
		for (int i = 0; i < this.size(); i++) {
			list.add(asT(this.get(i), that.get(i)));
		}
		return list;
	}

	@Override
	public <B> ListX<Tuple2<A, B>> zip(F1<A, B> f) {
		ListX<B> other = this.map(f);
		return this.zip(other);
	}

	public ListX<Tuple2<A, Integer>> zipWithIndex() {
		ListX<Tuple2<A, Integer>> list = createNewList(this.size());
		for (int i = 0; i < this.size(); i++) {
			A a = this.get(i);
			list.add(asT(a, i));
		}
		return list;
	}

	public ListX<A> takeRight(int n) {
		int size = size();
		return subList(max(size - n, 0), size);
	}

	public ListX<A> takeLeft(int n) {
		int size = size();
		return subList(0, min(size, n));
	}

	public ListX<A> takeWhile(F1B<A> f) {
		ListX<A> list = createNewList();
		for (A a : this) {
			if (!f.apply(a)) {
				return list;
			} else {
				list.add(a);
			}
		}
		return list;
	}

	public Tuple2<ListX<A>, ListX<A>> span(F1B<A> f) {
		ListX<A> trueList = createNewList(size() / 2);
		ListX<A> falseList = createNewList(size() / 2);
		for (A a : this) {
			if (f.apply(a)) {
				trueList.add(a);
			} else {
				falseList.add(a);
			}
		}
		return asT(trueList, falseList);
	}

	@Override
	public ListX<A> reverse() {
		ListX<A> reverted = createNewList(this.size());
		Iterator<A> ri = rightIterator();
		while (ri.hasNext()) {
			reverted.add(ri.next());
		}
		return reverted;
	}

	@Override
	public ListX<A> addX(A a) {
		ListX<A> list = createNewList(this);
		list.add(a);
		return list;
	}

	@Override
	public ListX<A> addX(Collection<? extends A> a) {
		ListX<A> list = createNewList(this);
		list.addAll(a);
		return list;
	}

	@Override
	public ListX<A> addX(A... a) {
		return addX(CollectionF.asList(a));
	}

	@Override
	public ListX<A> removeX(A a) {
		ListX<A> list = createNewList(this);
		list.remove(a);
		return list;
	}

	@Override
	public ListX<A> removeX(A... a) {
		return removeX(CollectionF.asList(a));
	}

	@Override
	public ListX<A> removeX(Collection<? extends A> a) {
		ListX<A> list = createNewList(this);
		list.removeAll(a);
		return list;
	}

	@Override
	public ListX<A> add2This(A a) {
		this.add(a);
		return this;
	}

	@Override
	public ListX<A> add2This(A... a) {
		return add2This(CollectionF.asList(a));
	}

	@Override
	public ListX<A> add2This(Collection<? extends A> a) {
		this.addAll(a);
		return this;
	}

	@Override
	public ListX<A> add2This(int index, A a) {
		this.add(index, a);
		return this;
	}

	@Override
	public ListX<A> add2This(int index, A... a) {
		return this.add2This(index, CollectionF.asList(a));
	}

	@Override
	public ListX<A> add2This(int index, Collection<? extends A> a) {
		this.addAll(index, a);
		return this;
	}

	@Override
	public boolean add(int index, A... col) {
		return this.addAll(index, CollectionF.asList(col));
	}

	@Override
	public ListX<A> removeAtX(Integer... at) {
		return this.createNewList(this).removeAtFromThis(at);
	}

	@Override
	public ListX<A> removeAtFromThis(Integer... at) {
		ListX<Integer> ids = CollectionF.asList(at).removeDuplicates().sort();
		for (int i = 0; i < at.length; i++) {
			int index = ids.get(i) - i;
			if (index < 0 || index >= this.size()) throw new IndexOutOfBoundsException("Can't remove element on index: " + (index + i));
			try {
				this.remove(index);
			} catch (IndexOutOfBoundsException e) {
				throw new IndexOutOfBoundsException("Can't remove element on index: " + (index + i));
			}
		}
		return this;
	}

	public A getRight(int index) {
		int size = size();
		if (index < 0 || index >= size) throw new IllegalArgumentException();
		return getBaseCollection().get(size - index - 1);
	}

	@Override
	public ListX<A> removeAtX(Collection<Integer> a) {
		return createNewList(this).removeAtFromThis(a);
	}

	@Override
	public ListX<A> removeAtFromThis(Collection<Integer> a) {
		return removeAtX(a.toArray(new Integer[a.size()]));
	}

	public boolean addAll(int index, Collection<? extends A> c) {
		return getBaseCollection().addAll(c);
	}

	public A get(int index) {
		return getBaseCollection().get(index);
	}

	public A set(int index, A element) {
		return getBaseCollection().set(index, element);
	}

	public void add(int index, A element) {
		getBaseCollection().add(index, element);
	}

	public A remove(int index) {
		return getBaseCollection().remove(index);
	}

	public int indexOf(Object o) {
		return getBaseCollection().indexOf(o);
	}

	public int lastIndexOf(Object o) {
		return getBaseCollection().lastIndexOf(o);
	}

	public ListIterator<A> listIterator() {
		return getBaseCollection().listIterator();
	}

	public ListIterator<A> listIterator(int index) {
		return getBaseCollection().listIterator(index);
	}

	public ListX<A> subList(int fromIndex, int toIndex) {
		return createNewList(getBaseCollection().subList(fromIndex, toIndex));
	}

	public ListX<A> subList(int fromIndex) {
		return createNewList(getBaseCollection().subList(fromIndex, this.size()));
	}
}
